--- 战斗主类
---
--- @class SkillClientManager
SkillClientManager = ClientFight.CreateClass("SkillClientManager")
ClientFight.SkillManager = SkillClientManager
local this = SkillClientManager
---@type table<string, ISkillEffect>
this.skillEffects = {};
--- @public
--- @return SkillClientManager
--[[
* 技能结束时候触发
]]
this.FINISH_TRIGGER = -1;
--[[
* 技能命中时候触发
]]
this.HIT_TRIGGER = -2;
--[[
* 立即触发
]]
this.QUICK_TRIGGER = -3;
--[[
* 获取buff配置 ,
* 这里需要一个 source 的原因是 , 奇异碎片的buff 是有动态参数的
* 所以在这里就需要把动态参数给替换掉 2021-05-13
*
* @param id
* @param source
* @return
]]
function this.putSkillEffect(skillId, skillEffect)
    this.skillEffects[skillId] = skillEffect;
end

function this.getSkillEffect(skillId)
    return this.skillEffects[skillId];
end

function this.getSkillShowBean(id, source)
    local bean = ClientFight.DataManager.skillShowMap[tostring(id)];
    return clone(bean);
end

function this.getSkillBean(id, source)
    if (source == nil) then
        local bean = ClientFight.DataManager.t_skillBeanMap[id];
        if (bean == nil) then
            --if (ClientFight.FightManager.isDebug()) then
            --
            --else
                --非debug环境, 直接返回nil, 就让他buff添加失败, 没必要让战斗出错导致玩家本场战斗失效
                printError("T_skillBean template not found, id: " .. id);
                return nil;
            --end
        end
        return bean;
    end
    local t_buffBean = source.skillDynamic[id];
    if (t_buffBean ~= nil) then
        return t_buffBean;
    end
    local bean = ClientFight.DataManager.t_skillBeanMap[id];
    if (bean == nil) then
        if (ClientFight.FightManager.isDebug()) then
        else
            --非debug环境, 直接返回nil, 就让他buff添加失败, 没必要让战斗出错导致玩家本场战斗失效
            printError("T_skillBean template not found, id: " .. id);
            return nil;
        end
    end
    if (not bean.dynamicParamSkill) then
        return bean;
    end
    bean = bean.cloneInstance();
    local b = bean.dynamicParamParse(source);
    if (not b) then
        if (ClientFight.FightManager.isDebug()) then
        else
            printError("T_skillBean dynamicParamParse error, id: " .. id);
        end
        return nil;
    end
    --成功了, 缓存这个buff, 没必要每次使用都去动态解析
    source.skillDynamic[bean.f_SkillID] = bean;
    return bean;
end


--[[
* 创建技能信息
]]
function this.createFightSkill(fight, skillId, fighter)
    skillId = this.transverterArmSkill(fighter, skillId);
    if (skillId == 0) then
        return nil;
    end
    local skillShowBean = this.getSkillShowBean(skillId)
    local skillBean = this.getSkillBean(skillId, fighter);
    if (skillBean == nil) then
        return nil;
    end
    local skill = FightSkill.New();
    skill.skillId = skillId;
    skill.skillBean = skillBean;
    skill.skillShowBean = skillShowBean;
    if (skill.skillBean.f_CircleRangeLimit and skill.skillBean.f_CircleRangeLimit ~= 0) then
        skill.attackRadius = skill.skillBean.f_CircleRangeLimit / Global.TEN_THOUSANDTH;
    end
    if (skill.skillBean.f_RangeLimit ~= nil) then
        skill.attackShape = Rectangle2.New(skill.skillBean.f_RangeLimit);
    end
    if (skill.skillBean.f_CircleRadius ~= nil) then
        skill.damageShape = Rectangle2.New(skill.skillBean.f_CircleRadius);
    end
    return skill;
end
addOverload(this, "addSkill", function(fight, skill)
    table.insert(fight.skills, skill);
end, "table", "table")

function this.removeSkill(fight, skill)
    table.ArrayRemoveByValue(fight.skills, skill)
end
---@return FightUsingSkill
function this.getRootSkill(skill)
    local rootSkill = skill.parentSkill;
    if (rootSkill == nil) then
        return skill;
    end
    while (rootSkill ~= rootSkill.parentSkill) do
        if (rootSkill.parentSkill == nil) then
            return rootSkill;
        end
        rootSkill = rootSkill.parentSkill;
    end
    return rootSkill;
end

--[[
* 增加技能产生效果帧数
*
* @param triggerFrame 触发帧数
]]
function this.addSkillEffectAction(skill, triggerFrame, effectAction)
    if (not skill.actionFrames[triggerFrame]) then
        skill.actionFrames[triggerFrame] = {};
    end
    if (not skill.actionUniqueId[skill.skillUniqueId]) then
        skill.actionUniqueId[skill.skillUniqueId] = {};
    end
    if (not skill.actionUniqueId[skill.skillUniqueId][triggerFrame]) then
        skill.actionUniqueId[skill.skillUniqueId][triggerFrame] = {};
    end
    table.insert(skill.actionFrames[triggerFrame], effectAction);
    table.insert(skill.actionUniqueId[skill.skillUniqueId][triggerFrame], effectAction)
end

--[[
* 使用技能
]]
---@param fight Fight
---@param fighter Fighter
function this.useSkill(fight, fighter, target, skill, isDo)

    local skillBean = skill.skillBean;
    if skillBean.f_SkillID == 132400 then
        local a = 1;
    end
    if isDo then
    end
    SkillRoleMgr.savePathFile("[" .. fight.frame .. "][SKILL]" .. fighter.fighterId .. "准备对" .. target.fighterId .. "攻击，使用技能" .. skillBean.f_SkillID)
    --.. " 攻击者坐标: " .. fighter.position .. "  目标的坐标: " .. target.position);
    local useSp = fighter:calUseSp(skill);
    if (useSp > 0) then
        ClientFight.FighterManager.addEnergy(fight, fighter, skill.skillId, 0, -useSp, EnergyChangeReasonEnum.USE_SKILL);
        -- 大招，暂停次数+1
        fight.pause = fight.pause + 1;
    end

    local usingSkill = FightUsingSkill.New();
    usingSkill.skillUniqueId = fight.skillIds;
    fight.skillIds = fight.skillIds + 1;
    usingSkill.fight = fight;
    usingSkill.fighter = fighter;
    usingSkill.target = target;
    usingSkill.skill = skill;
    usingSkill.parentSkill = usingSkill;
    usingSkill.speed = this.countSkillSpeed(usingSkill);
    this.addSkill(fight, usingSkill);

    -- 触发攻击开始事件
    ClientFight.EventManager.notifyEventListener(fight, FightEventEnum.BEFORE_ATTACK, fighter.fighterId, usingSkill.target, usingSkill);
    if (fighter.usingSkill ~= nil) then
        --说明在上一行这个过程中触发了一个非分离技,那么本次技能释放中断,等下次再放
        if isDo then
            --手动释放大招强制停止当前技能
            this.finishSkill(fighter.usingSkill, true)
        else
            if (usingSkill.skill.skillId ~= fighter.usingSkill.skill.skillId) then
                this.removeSkill(fight, usingSkill);
            end
            return ;
        end
    end
    if (usingSkill.originalTargetPlacePosition == nil) then
        usingSkill.originalTargetPlacePosition = Vector2.New(usingSkill.target.position.x, usingSkill.target.position.y);
    end
    local attack = ReportManager.attackToJsonString(fight, fighter, usingSkill.target, usingSkill, usingSkill.speed);
    table.insert(fight.actions, attack);
    FightManager.playActions(attack, skill)
    if (skillBean.f_Speed ~= 0 or skillBean.f_moveFrame ~= 0) then
        fighter.moveAttackAction = attack;
    end

    -- 开始释放技能
    prepareSkill(usingSkill, fighter.position);

    local team = fight.teams[fighter.teamIndex];
    if (not team.frozen) then
        return ;
    end

    usingSkill.skipFrame = math.ceil(skillBean.f_BulletTime / usingSkill.speed / Global.FIGHT_FRAME);

    -- 聚光灯播放效果
    if (usingSkill.stage ~= FightSkillStageEnum.FINISH and usingSkill.frame < usingSkill.skipFrame) then
        fight.addFrozenSkills(usingSkill);
    end
end

--[[
* 释放支援技能
]]
function this.useSupportSkill(fight, teamIndex)
    local fightTeam = fight.teams[teamIndex];
    if (fightTeam == nil or fightTeam.supportSkill == nil) then
        return ;
    end
    local teamFighter = fightTeam.teamFighter;
    local target = selectAttackTarget(fight, teamFighter, fightTeam.supportSkill, teamFighter.position, nil, false);

    --if (target == nil) then
    --    LogUtil.info( string.format("[%s]释放支援技能%s没有找到目标", fight.frame, fightTeam.getSupportSkill().skillId));
    --return;
    --}

    SkillRoleMgr.savePathFile(string.format("[%s]准备释放支援技能%s,目标%s", fight.frame, fightTeam.getSupportSkill().skillId, target.fighterId));
    useSkill(fight, teamFighter, target, fightTeam.supportSkill);
    fightTeam.supportSkill = nil;
end

--[[
* 技能触发技能
]]
function this.triggerSkill(parentSkill, target, triggerSkillId)
    local fight = parentSkill.fight;
    local fighter = parentSkill.fighter;
    if (fighter.dieStatus) then
        return false;
    end
    -- 生成基础触发技能信息
    local fskill = createFightSkill(fight, triggerSkillId, fighter);
    if (fskill == nil) then
        return false;
    end
    if (not SkillLimitType:action(fighter, fskill.skillBean)) then
        return false;
    end

    local triggerSkill = FightUsingSkill.New();
    triggerSkill.skillUniqueId = fight.skillIds;
    fight.skillIds = fight.skillIds + 1;
    triggerSkill.fight = fight;
    triggerSkill.fighter = fighter;
    triggerSkill.skill = fskill;
    triggerSkill.parentSkill = parentSkill;
    triggerSkill.speed = this.countSkillSpeed(triggerSkill);
    if (parentSkill.skill ~= nil and parentSkill.skill.ruleSelectorByBuff ~= nil) then
        triggerSkill.skill.ruleSelectorByBuff = parentSkill.skill.ruleSelectorByBuff;
    end
    if (parentSkill.skill ~= nil) then
        triggerSkill.skill.skillType = parentSkill.skill.skillType;
    end
    local skillBean = fskill.skillBean;

    local skillEffect = ClientFight.SkillClientManager.getSkillEffect(skillBean.f_SkillScript);
    if (skillEffect ~= nil) then
        local result = skillEffect:triggerSkill(parentSkill, target, triggerSkill);
        if (result.breakRelease) then
            return result.succ;
        end
    end

    -- 整个技能不可重复目标
    if (skillBean.f_SkillPointRepeat == SkillPointRepeatEnum.UNREPEATABLE_ALL) then
        table.insert(parentSkill.excludes, parentSkill.target.fighterId);
    end
    -- 把所有打过的目标都记在最上面的技能上面
    --      System.out.println("parentSkill:" .. parentSkill.skill.skillId);
    if (parentSkill.target ~= nil) then
        table.insert(getRootSkill(parentSkill).attackedTargets, parentSkill.target.fighterId);
        if (parentSkill ~= getRootSkill(parentSkill)) then
            -- 父技能上面也记一个
            table.insert(parentSkill.attackedTargets, parentSkill.target.fighterId);
        end
    end

    -- 触发技能选择目标
    if (target == nil) then
        if (skillBean.f_TargetRule == SkillTargetRuleEnum.PARENTSKILL_TARGET) then
            target = parentSkill.target;
            -- 目标死亡时重新选择目标（skill.skill==nil为buff触发）
            if (skillBean.f_dieContinue == SkillContinueTypeEnum.RESELECTION_ON_TARGETDIE and parentSkill.skill ~= nil) then
                local reselect = false;
                if (target:isDie()) then
                    reselect = true;---目标死了,直接结束技能
                else
                    if (target.heroBean.f_HeroID == 610) then
                        if (table.contains(target.totalSign.signs, FighterSignEnum.TAG_998)) then
                            reselect = true; --如果是西泽, 西泽在死亡复活阶段, 也直接结束技能
                        end
                    end
                end
                if (reselect) then

                    local parentSkillBean = parentSkill.skill.skillBean;
                    if (parentSkillBean.f_SkillPointRepeat ~= SkillPointRepeatEnum.REPEATABLE) then
                        target = selectAttackTarget(fight, fighter, parentSkill.skill, parentSkill.position, parentSkill.excludes, true);
                    else
                        target = selectAttackTarget(fight, fighter, parentSkill.skill, parentSkill.position, nil, true);
                    end

                end
            else
                --                    -- 吸引普攻子弹
                --                    if (skillBean.f_SkillType == 1 and skillBean.getF_BulletTpye() == 1) {
                --                        List<Fighter> others = FighterUtils.getOthersBy(fighter, true, nil);
                --                        for (Fighter other : others) {
                --                            if (FighterStateEnum.is(other, FighterStateEnum.ATTRACT_NORMAL_BULLET)) {
                --                                target = other;
                --                                break;
                --                            }
                --                        }
                --                    }
            end
        elseif (skillBean.f_SkillPointRepeat ~= SkillPointRepeatEnum.REPEATABLE) then
            target = selectAttackTarget(fight, fighter, fskill, parentSkill.position, parentSkill.excludes, true);
        else
            target = selectAttackTarget(fight, fighter, fskill, parentSkill.position, nil, true);
        end
    end

    if (target == nil) then
        local skillId = 0;
        if (parentSkill.skill ~= nil) then
            skillId = parentSkill.skill.skillBean.f_SkillID;
        end
        local logSkill = skillId;
        SkillRoleMgr.savePathFile("[" .. fight.frame .. "][SKILL]" .. fighter.fighterId .. "父技能" .. logSkill .. "子技能" .. triggerSkill.skill.skillId .. "因为没有目标触发技能失败");
        return false;
    end

    -- 死亡后不继续释放
    if (skillBean.f_dieContinue == SkillContinueTypeEnum.STOP_ON_TARGETDIE) then
        local finish = false;
        if (target:isDie()) then
            finish = true;--目标死了,直接结束技能
        else
            if (target.heroBean.f_HeroID == 610) then
                if (table.contains(target.totalSign.signs, FighterSignEnum.TAG_998)) then
                    finish = true; --如果是西泽, 西泽在死亡复活阶段, 也直接结束技能
                end
            end
        end
        if (finish) then
            SkillRoleMgr.savePathFile("[" .. fight.frame .. "][SKILL]" .. fighter.fighterId .. "父技能" .. parentSkill.skill == nil and "0" or parentSkill.skill.skillBean.f_SkillID .. "子技能" .. triggerSkill.skill.skillId .. "因为没有目标死亡技能失败");
            return false;
        end
    end

    triggerSkill.target = target;

    -- 整个技能不可重复目标
    if (skillBean.f_SkillPointRepeat == SkillPointRepeatEnum.UNREPEATABLE_ALL) then
        triggerSkill.excludes = parentSkill.excludes;
    end

    -- 目标不可重复
    if (skillBean.f_SkillPointRepeat ~= SkillPointRepeatEnum.REPEATABLE) then
        table.insert(parentSkill.excludes, target.fighterId);
    end

    -- 如果非分离技能，打断所有正在使用的非分离技能
    if (skillBean.f_SkillOut ~= 1 and fighter.usingSkill ~= nil) then
        this.finishSkill(fighter.usingSkill, true, FinishSkillReasonEnum.USE_SKILL);
    end
    triggerSkill.originalTargetPlacePosition = Vector2.New(target.position.x, target.position.y);
    if (skillBean.f_SkillOut == 1) then
        local frameInfoCommand = ReportManager.flyToJsonString(fight, fighter, target, triggerSkill);
        table.insert(fight.actions, frameInfoCommand);
        FightManager.playActions(frameInfoCommand, triggerSkill)
    else
        local attack = ReportManager.attackToJsonString(fight, fighter, target, triggerSkill, triggerSkill.speed);
        table.insert(fight.actions, attack);
        FightManager.playActions(attack)
        if (skillBean.f_Speed ~= 0 or skillBean.f_moveFrame ~= 0) then
            fighter.moveAttackAction = attack;
        end
    end

    this.addSkill(fight, triggerSkill);
    local logTarget = target;
    SkillRoleMgr.savePathFile("[" .. fight.frame .. "][SKILL]" .. fighter.fighterId .. "对" .. logTarget.fighterId .. "触发子技能" .. triggerSkill.skill.skillBean.f_SkillID);

    -- 开始释放技能
    prepareSkill(triggerSkill, parentSkill.position);

    -- 子技能加速
    if (parentSkill.frame < parentSkill.skipFrame) then
        triggerSkill.skipFrame = parentSkill.skipFrame - parentSkill.frame;
    end
    -- 聚光灯播放效果
    if (triggerSkill.stage ~= FightSkillStageEnum.FINISH and triggerSkill.frame < triggerSkill.skipFrame) then
        fight.frozenSkills = triggerSkill;
    end
    return true;
end

--[[
* 技能前摇阶段
]]
function this.prepareSkill(skill, position)
    local fighter = skill.fighter;
    local target = skill.target;
    local skillBean = skill.skill.skillBean;
    local originalTargetPlacePoint = Vector2.New(target.position.x, target.position.y);
    skill.originalTargetPlacePosition = originalTargetPlacePoint;
    local skillEffect = ClientFight.SkillClientManager.getSkillEffect(skillBean.f_SkillScript);
    if (skillEffect ~= nil) then
        if (skillEffect:prepareSkill(skill, position)) then
            return ;
        end
    end

    -- 前摇阶段
    skill.stage = FightSkillStageEnum.PREPARE;
    skill.separate = skillBean.f_SkillOut == 1;
    -- 指向左或右方向
    skill.targetDirection = FighterUtils.leftOrRight1(fighter, target);
    if (skill.targetPosition == nil and skillBean.f_SkillPoint == SkillTargetDirectingEnum.PLACE) then
        skill.targetPosition = Vector2.New(target.position.x, target.position.y);
        -- 和父技能相同
    elseif (skillBean.f_SkillPoint == SkillTargetDirectingEnum.PARENT) then

        if (skill.targetPosition == nil and skill.parentSkill.targetPosition ~= nil) then
            skill.targetPosition(Vector2.New(skill.parentSkill.targetPosition.x, skill.parentSkill.targetPosition.y));
        end
        skill.targetDirection = skill.parentSkill.targetDirection;
    end
    local pos = Vector2.New(position.x, position.y);
    if (skill.separate and skillBean.f_Speed == 0 and skillBean.f_moveFrame == 0) then
        if (skillBean.f_SkillPoint == SkillTargetDirectingEnum.TARGET or skillBean.f_SkillPoint == SkillTargetDirectingEnum.PLACE) then
            pos = Vector2(target.position.x, target.position.y);
        elseif (skillBean.f_SkillPoint == SkillTargetDirectingEnum.PARENT and skill.parentSkill ~= nil) then
            local parentSkillBean = skill.parentSkill.skill.skillBean;
            if (parentSkillBean.f_SkillPoint == SkillTargetDirectingEnum.TARGET or parentSkillBean.f_SkillPoint == SkillTargetDirectingEnum.PLACE) then
                pos = Vector2.New(target.position.x, target.position.y);
            end
        end
    end

    if (skillBean.f_SkillShifting ~= nil) then
        --技能偏移
        pos.x = pos.x + skill.targetDirection * skillBean.f_SkillShifting[1];
        pos.y = pos.y + skillBean.f_SkillShifting[2];
    end
    -- 设置起始坐标
    skill.position = pos;
    skill.startPosition = Vector2.New(pos.x, pos.y);

    if (skillBean.f_SkillShape ~= nil) then
        skill.skillShape = Rectangle2.New(skillBean.f_SkillShape);
    else
        skill.skillShape = fighter.shape;
    end

    if (not skill.separate) then
        fighter.usingSkill = skill;
    end

    local prepareTime = 0-- skill.skill.skillBean.f_PrepareTime or 0;
    if (prepareTime > 0) then
        local frame = math.ceil(prepareTime / skill.speed / Global.FIGHT_FRAME); -- 计算攻击前摇帧数
        if (frame > 0) then
            skill.stageFinishFrame = frame;
            return ;
        end
    end

    this.releaseSkill(skill);
end

function this.calSkillSpeed(fighter, skillBean)
    if (skillBean.f_SkillOut == 1) then
        return 1;
    end
    if (fighter:isSummons()) then
        local summonsCreater = fighter.summonsCreater;
        if table.contains(summonsCreater.totalSign.signs, FighterSignEnum.TAG_996) then
            fighter = summonsCreater;--勒苟拉斯集火状态,强行使用主人攻速
        end
    end
    local atkSpeed = FighterAttributeManager.getFightAttr(fighter, FighterAttributeEnum.ATTACK_SPEED);
    if (atkSpeed > 20000) then
        atkSpeed = 20000;
    end
    local agility = FighterAttributeManager.getFightAttr(fighter, FighterAttributeEnum.SPEED);

    --计算灵敏
    local SPEED = 1 + Utils.toRatio(agility);
    if (agility < 0) then
        SPEED = 1 / (1 - Utils.toRatio(agility));
    end
    -- 注 : 时长 = 时长/speed , 所以速度越大, 释放技能越快
    if (skillBean.f_SkillType == SkillTypeEnum.NORMAL_ATTACK) then
        if (atkSpeed >= 0) then
            return Utils.toRatio(fromRatio(SPEED * (1 + Utils.toRatio(atkSpeed))));
        else
            --负值的 攻速 , 需要用另外一套算法 , 否则攻速为0的情况下, 会无限放大技能释放时长
            local atkSp = 1 / (1 - toRatio(atkSpeed));
            return Utils.toRatio(fromRatio(SPEED * atkSp));
        end
    else
        return Utils.toRatio(fromRatio(SPEED));
    end
end
--[[
* 计算技能加速
]]
addOverload(this, "countSkillSpeed", function(fighter, skillBean)
    local speed = calSkillSpeed(fighter, skillBean);
    if (speed < Global.FIGHT_SPEED_MIN_LIMIT) then
        speed = Global.FIGHT_SPEED_MIN_LIMIT;
    end
    return speed;
end, "table", "table")

--[[
* 计算技能加速
]]

addOverload(this, "countSkillSpeed", function(skill)
    return this.countSkillSpeed(skill.fighter, skill.skill.skillBean);
end, "table")
--[[
* 计算技能前置冷却
]]
function this.countSkillPreCD(fighter, skillBean)
    if (skillBean.f_SkillType == SkillTypeEnum.NORMAL_ATTACK) then
        return 0;
    end

    local cdIni = skillBean.f_CdIni;
    local cdReduce = FighterAttributeManager.getFightAttr(fighter, FighterAttributeEnum.CD);
    local speed = this.countSkillSpeed(fighter, skillBean);

    return math.ceil(cdIni * (1 - Utils.toRatio(cdReduce)) / speed);
end


--[[
* 计算技能前置冷却
]]
function this.countOriginalSkillPreCD(fighter, skillBean)
    if (skillBean.f_SkillType == SkillTypeEnum.NORMAL_ATTACK) then
        return 0;
    end

    local cdIni = skillBean.f_CdIni;
    local cdReduce = 0;
    local speed = this.countSkillSpeed(fighter, skillBean);

    return math.ceil(cdIni * (1 - Utils.toRatio(cdReduce)) / speed);
end

--[[
* 计算技能冷却
]]
function this.countSkillCD(skill)
    local fighter = skill.fighter;
    local skillBean = skill.skill.skillBean;
    local speed = this.countSkillSpeed(skill);--释放完技能,需要重新计算速度,不能用 skill.getSpeed

    local castTime = skillBean.f_PrepareTime + skillBean.f_GuideTime;
    local cd = skillBean.f_Cd;
    local o = fighter.parametersByObject[BuffEffect2001140.BuffEffect2001140];
    if (o ~= nil) then
        cd = cd - (o[skill.skill.skillBean.f_skillId] or 0)
    end
    local cdReduce = FighterAttributeManager.getFightAttr(fighter, FighterAttributeEnum.CD);
    if BuffEffect89 then
        local o89 = fighter.parametersByObject[BuffEffect89.BuffEffect89];
        if (o89 ~= nil) then
            cdReduce = cdReduce + (o89[skill.skill.skillBean.f_skillId] or 0)
        end
    end

    if (skillBean.f_SkillType == SkillTypeEnum.NORMAL_ATTACK) then
        local totalCd = castTime + cd;
        local remainTime = math.ceil(totalCd * (1 - Utils.toRatio(cdReduce)) / speed);
        return remainTime > castTime and remainTime - castTime or 0;
    else
        return math.ceil(cd * (1 - Utils.toRatio(cdReduce)) / speed);
    end
end


--[[
* 计算技能的原始冷却时间, 也就是说, 不计算CD 加速的影响
]]
function this.countOriginalSkillCD(skill)
    local skillBean = skill.skill.skillBean;
    local speed = skill.speed;

    local castTime = skillBean.f_PrepareTime + skillBean.f_GuideTime;
    local cd = skillBean.f_Cd;
    local fighter = skill.fighter;
    local o = fighter.parametersByObject[BuffEffect2001140.BuffEffect2001140];
    if (o ~= nil) then
        cd = cd - o[skill.skill.skillBean.f_skillId] or 0;
    end
    local cdReduce = 0;

    if (skillBean.f_SkillType == SkillTypeEnum.NORMAL_ATTACK) then
        local remainTime = math.ceil((castTime + cd) / speed);
        return remainTime > castTime and remainTime - castTime or 0;
    else
        return math.ceil(cd * (1 - Utils.toRatio(cdReduce)) / speed);
    end
end

--[[
* 技能开始释放
]]
function this.releaseSkill(skill)

    local fight = skill.fight;
    local fighter = skill.fighter;
    local target = skill.target;
    local skillBean = skill.skill.skillBean;
    if skillBean.f_SkillID == 103 then
        local a = 1;
    end
    local skillEffect = ClientFight.SkillClientManager.getSkillEffect(skillBean.f_SkillScript);
    if (skillEffect ~= nil) then
        if (skillEffect:releaseSkill(skill)) then
            return ;
        end
    end

    -- 引导阶段
    skill.stage = FightSkillStageEnum.GUIDE;
    -- 增加能量
    if ( skillBean.f_GetSP ~= nil and skillBean.f_GetSP > 0) then
        ClientFight.FighterManager.addEnergy(fight, fighter, 0, 0, skillBean.f_GetSP, EnergyChangeReasonEnum.USE_SKILL);
    end

    -- 技能加速速度

    skill.moveSpeed = Utils.toRatio(skillBean.f_Speed) * skill.speed / (1000 / Global.FIGHT_FRAME);
    skill.moveFrame = skillBean.f_moveFrame or 0 / Global.FIGHT_FRAME;

    local frame = nil;

    -- 召唤物逻辑
    if (skillBean.f_SummonThing ~= nil) then
        createSummonAction(skill);
    end

    -- 技能释放时给自己加buff
    local buffs = skillBean.f_BuffBySkill;
    if (buffs ~= nil) then
        for i, buff in pairs(buffs) do

            if (buff[1] == 0) then
                ClientFight.BuffManager.addBuff(fight, skill, fighter, fighter, false, buff[2], buff[3], target, skill);
            else
                frame = math.ceil(buff[1] / skill.speed / Global.FIGHT_FRAME);
                local triggerBuffs = nil;
                local triggerFrame = frame;
                if (triggerFrame > 0) then
                    triggerFrame = triggerFrame + skill.frame;
                end
                if (skill.triggerBuffFrames[triggerFrame]) then
                    triggerBuffs = skill.triggerBuffFrames[triggerFrame];
                else
                    triggerBuffs = {};
                    skill.triggerBuffFrames[triggerFrame] = triggerBuffs;
                end
                triggerBuffs[buff[2]] = buff[3];
            end
        end
    end

    -- 计算技能触发帧数
    local triggers = skillBean.f_SkillBySkill;
    if (triggers ~= nil) then
        for i, trigger in pairs(triggers) do

            if (trigger[1] > 0) then
                frame = (math.ceil(trigger[1] / skill.speed / Global.FIGHT_FRAME));
            else
                frame = trigger[1];
            end
            if (frame ~= 0) then
                local int
                --logFrame = frame;
                SkillRoleMgr.savePathFile(string.format("[%s][SKILL]%s对%s使用技能%s在%s帧后触发子技能%s",
                        fight.frame, fighter.fighterId, target.fighterId, skillBean.f_SkillID, logFrame, trigger[1]
                ));
                local triggerSkills = {};
                local triggerFrame = frame;
                if (triggerFrame > 0) then
                    triggerFrame = triggerFrame + skill.frame;
                end
                if (skill.triggerFrames[triggerFrame]) then
                    triggerSkills = skill.triggerFrames[triggerFrame];
                else
                    triggerSkills = {};
                    skill.triggerFrames[triggerFrame] = triggerSkills;
                end
                table.insert(triggerSkills, trigger[2]);
            else
                SkillRoleMgr.savePathFile(string.format("[%s][SKILL]%s对%s使用技能%s立即触发子技能",
                        fight.frame, fighter.fighterId, target.fighterId, skillBean.f_SkillID
                ));
                this.triggerSkill(skill, nil, trigger[2]);
            end
        end
    elseif (skillEffect ~= nil) then
        skillEffect:triggerSkills(skill);
    end

    -- 计算伤害延迟帧数
    if (skillBean.f_SkillHit == SkillHitTypeEnum.TIME) then
        if (skillBean.f_DelayTimeByBuff ~= nil) then
            local quickTrigger = false;
            for i, onceAttack in pairs(skillBean.f_DelayTimeByBuff) do

                if (onceAttack[1] > 0) then
                    frame = math.ceil(onceAttack[1] / skill.speed / Global.FIGHT_FRAME);
                else
                    frame = onceAttack[1];
                end

                -- buff增加时机
                local addType = BuffAddTrigger.DAMAGE_BEFORE;
                if (table.getn(onceAttack) >= 4) then
                    addType = onceAttack[4];
                end

                local skillEffectAction = SkillEffectAction.New(onceAttack[2], onceAttack[3], addType);
                if (frame ~= 0) then
                    local triggerFrame = frame;

                    if (triggerFrame > 0) then
                        triggerFrame = triggerFrame + skill.frame;
                    end

                    this.addSkillEffectAction(skill, triggerFrame, skillEffectAction);
                    local logFrame = frame;
                    SkillRoleMgr.savePathFile("[" .. fight.frame .. "][SKILL]" .. fighter.fighterId .. "对" .. target.fighterId .. "使用技能" .. skillBean.f_SkillID .. "在" .. logFrame .. "帧后进行伤害效果判定");
                else
                    quickTrigger = true;
                    this.addSkillEffectAction(skill, QUICK_TRIGGER, skillEffectAction);
                end
            end
            if (quickTrigger) then
                this.effectSkill(skill, QUICK_TRIGGER);
                SkillRoleMgr.savePathFile("[" .. fight.frame .. "][SKILL]" .. fighter.fighterId .. "对" .. target.fighterId .. "使用技能" .. skillBean.f_SkillID .. "立即进行伤害效果判定");
            end
        end
    elseif (skillBean.f_SkillHit == SkillHitTypeEnum.HIT_TARGET or skillBean.f_SkillHit == SkillHitTypeEnum.HIT_ANY) then
        if (skillBean.f_DelayTimeByBuff ~= nil) then
            for i, onceAttack in pairs(skillBean.f_DelayTimeByBuff) do

                -- buff增加时机
                local addType = BuffAddTrigger.DAMAGE_BEFORE;
                if (table.getn(onceAttack) >= 4) then
                    addType = onceAttack[4];
                end
                local skillEffectAction = SkillEffectAction.New(onceAttack[2], onceAttack[3], addType);
                this.addSkillEffectAction(skill, this.HIT_TRIGGER, skillEffectAction);
            end
        end
    end

    if (skillBean.f_SkillOver == SkillFinishTypeEnum.TIME) then
        -- LogUtil.info( "[" .. fight.frame .. "][SKILL]" ..
        -- fighter.fighterId .. "对" .. target.fighterId
        -- .. "攻击，使用技能" .. skillBean.f_SkillID .. "技能释放时长：" ..
        -- skillBean.getF_GuideTime() * speed);
        -- 计算攻击结束延迟帧数
        frame = math.ceil(skillBean.f_GuideTime / skill.speed / Global.FIGHT_FRAME);
        if (frame > 0) then
            local logFrame = frame;
            SkillRoleMgr.savePathFile("[" .. fight.frame .. "][SKILL]" .. fighter.fighterId .. "对" .. target.fighterId .. "使用技能" .. skillBean.f_SkillID .. "在" .. logFrame .. "帧后结束技能");
            skill.stageFinishFrame = skill.frame + frame;
        else
            SkillRoleMgr.savePathFile("[" .. fight.frame .. "][SKILL]" .. fighter.fighterId .. "对" .. target.fighterId .. "使用技能" .. skillBean.f_SkillID .. "立即结束技能");
            this.finishSkill(skill, false, FinishSkillReasonEnum.NORMAL_FINISH);
        end
    else
        skill.stageFinishFrame = 2100000000-- Integer.MAX_VALUE;
    end
end

--[[
* 技能释放时准备添加召唤物
]]
function this.createSummonAction(skill)
    local fight = skill.fight;
    local fighter = skill.fighter;
    local skillBean = skill.skill.skillBean;
    local frame = nil;

    local summonFrame = skillBean.f_SummonThing[1]; -- 出现帧数
    local summonId = skillBean.f_SummonThing[2]; -- 召唤物id
    local liveTime = skillBean.f_SummonThing[3]; -- 存活时间
    local real = true;
    if (summonFrame > 0) then
        frame = math.ceil(summonFrame / skill.speed / Global.FIGHT_FRAME);
    else
        frame = summonFrame;
    end
    if (table.getn(skillBean.f_SummonThing) > 3) then
        if (skillBean.f_SummonThing[3] == 1) then
            real = false;
        end
    end
    local summNum = 1;
    if (table.getn(skillBean.f_SummonThing) > 4) then
        summNum = skillBean.f_SummonThing[5];
    end
    local summonAction = SkillCreateSummonAction.New(summonId, liveTime, real, summNum);

    if (frame ~= 0) then
        local triggerFrame = frame;
        if (triggerFrame > 0) then
            triggerFrame = triggerFrame + skill.frame;
        end
        skill:addSummonFrames(summonAction, triggerFrame);
        SkillRoleMgr.savePathFile(string.format("[%s][SKILL]%s的技能%s在%s帧后召唤%s", fight.frame, fighter.fighterId, skillBean.f_SkillID, frame, summonId));
    else
        skill:addSummonFrames(summonAction, QUICK_TRIGGER);
        SkillRoleMgr.savePathFile(string.format("[%s][SKILL]%s的技能%s立即召唤%s", fight.frame, fighter.fighterId, skillBean.f_SkillID, summonId));
    end
end

--[[
* 检查并创建召唤物
]]
function this.checkAndCreateSummon(triggerFrame, skill)
    local summonAction = skill.createSummonFrames[triggerFrame];
    if (summonAction == nil) then
        return ;
    end
    for i = 1, summonAction.summNum do
        local skillBean = skill.skill.skillBean;
        local fighter = skill.fighter;
        local summon = ClientFight.FighterManager.createSummonsFighter(skill.fight, fighter, summonAction.summonModelId, skillBean.f_SummonAiType, summonAction.isReal and SummonsType.ENTITY or SummonsType.UN_ENTITY, skillBean.f_SummonThingAttribute, summonAction.lifetime);
        if (summon == nil) then
            return ;
        end

        local index = math.min(table.getn(skillBean.f_SpawnPositionType) - 1, i);
        local spawnPositionType = SpawnPositionType:valueOf(skillBean.f_SpawnPositionType[i][1]);
        local position = spawnPositionType:action(summon, skill, skillBean.f_SpawnPositionType[i]);
        summon.position = position;
        if (spawnPositionType ~= SpawnPositionType.type6) then
            ClientFight.FighterManager.checkOut(summon, Global.FIGHT_INNER_ROUND);
        end
        --        ClientFight.FighterManager.changePosition(skill.fight, summon, position, 0, true, true);

        local skillEffect = ClientFight.SkillClientManager.getSkillEffect(skill.skill.skillBean.f_SkillScript);
        if (skillEffect ~= nil) then
            skillEffect:summonEnterSceneBefore(skill, summon);
        end
        ClientFight.FighterManager.fighterEnterScene(skill.fight, summon, skill, 1);

        SkillRoleMgr.savePathFile(string.format("[%s]" .. "技能%s创建创建召唤物成功,fightId=%s,坐标点=%s, {%s}",
                skill.fight.frame, skill.skill.skillBean.f_skillId, summon.fighterId, summon.position, summon.totalAttribute));
    end
end

--[[
* 完成技能
*
* @param breakin 是否为因为被阻断才完成技能
]]
function this.finishSkill(skill, breakin, reason)
    local skillBean = skill.skill.skillBean;

    local skillEffect = ClientFight.SkillClientManager.getSkillEffect(skillBean.f_SkillScript);
    if (skillEffect ~= nil) then
        if (skillEffect:finishSkill(skill, breakin)) then
            return ;
        end
    end

    local fight = skill.fight;
    local fighter = skill.fighter;
    local target = skill.target;

    -- 技能已经结束
    if (skill.stage == FightSkillStageEnum.FINISH) then
        return ;
    end
    if (not skill.separate) then
        fighter.usingSkill = nil;
    end
    skill.stage = FightSkillStageEnum.FINISH;
    -- 聚光灯播放效果
    if (skill.frame <= skill.skipFrame) then
        table.ArrayRemoveByValue(fight.frozenSkills)
    end

    if (not breakin) then
        -- 增加攻击后buff
        if (not target:isDie()) then
            if (skill.triggerBuffFrames[this.FINISH_TRIGGER]) then
                local triggerBuffs = skill.triggerBuffFrames[this.FINISH_TRIGGER];
                for buffId, buffOverlay in pairs(triggerBuffs) do
                    ClientFight.BuffManager.addBuff(fight, skill, fighter, target, false, buffId, buffOverlay, target, skill);
                end ;
            end
        end

        checkAndCreateSummon(this.FINISH_TRIGGER, skill);
    end

    local addCD = true;
    if (skill.skill == fighter.normalAttack) then
        if (target ~= nil and target:isDie()) then
            --被我的普攻打死了, 不需要增加CD
            addCD = false;
        end
    end
    if (addCD and skillBean.f_Cd and skillBean.f_Cd > 0) then
        -- 增加CD
        local cd = countSkillCD(skill);
        local originalCd = countOriginalSkillCD(skill);
        addCooldown(fighter, skillBean.f_SkillID, cd / Global.FIGHT_FRAME, originalCd / Global.FIGHT_FRAME);
    end

    -- 触发攻击结束事件
    ClientFight.EventManager.notifyEventListener(fight, FightEventEnum.AFTER_ATTACK, fighter.fighterId, target, skill);
    if (skill.skill.skillBean.f_SkillBySkill == nil) then
        --没有子技能了
        ClientFight.EventManager.notifyEventListener(fight, FightEventEnum.AFTER_ATTACK_FULL, fighter.fighterId, target, skill);
    end

    SkillRoleMgr.savePathFile(string.format("[%s][SKILL][%s]%s对%s使用技能%s结束技能,结束原因%s", fight.frame, skill.frame, fighter.fighterId, target.fighterId, skillBean.f_SkillID, reason));

    -- 技能结束时给自己加buff
    if (not breakin) then
        local buffs = skillBean.f_BuffBySkill;
        if (buffs ~= nil) then
            for i, buff in pairs(buffs) do

                if (buff[1] == -1) then
                    ClientFight.BuffManager.addBuff(fight, skill, fighter, fighter, false, buff[2], buff[3], target, skill);
                end
            end
        end
    end

    removeSkill(fight, skill);

    if (fighter.moveAttackAction ~= nil and ReportManager.isMoveAttackOf(skill, fighter.moveAttackAction)) then
        ReportManager.setMoveAttackEndToJsonString(fight, fighter, fighter.moveAttackAction);
        fighter.moveAttackAction = nil;
    end

    if (not breakin) then
        -- 技能发生作用
        if (skill.actionFrames[this.FINISH_TRIGGER]) then
            this.effectSkill(skill, this.FINISH_TRIGGER);
        end

        -- 技能触发技能
        if (skill.triggerFrames[this.FINISH_TRIGGER]) then
            local triggerSkills = skill.triggerFrames[this.FINISH_TRIGGER];
            for i, tSkill in pairs(triggerSkills) do

                this.triggerSkill(skill, nil, tSkill);
            end
        end
    else
        if (skill.effectTimes <= 0 and skill.skill.skillBean.f_AreaTarget == TargetAreaCenterType.type8) then
            --特殊处理 技能被打断后如果技能中心点为type8的需要重新设置中心点告诉客户端
            local frameInfo = ReportManager.lockSkillReleasPosition(skill.fight, skill.fighter, skill.target,
                    skill.originalTargetPlacePosition, skill);
            table.insert(skill.fight.actions, frameInfo);
            FightManager.playActions(frameInfo)
        end
    end
    if (skillEffect ~= nil) then
        skillEffect:afterFinishSkill(skill, breakin);
    end
end

--[[
* 移动技能
]]
function this.moveSkill(skill)
    local fight = skill.fight;
    local fighter = skill.fighter;

    local arrive = false;
    local skillBean = skill.skill.skillBean;
    if (skillBean.f_SkillPoint == SkillTargetDirectingEnum.DIRECTION) then
        SkillRoleMgr.savePathFile(string.format("[%s][SKILL]%s的技能%s准备按方向移动", fight.frame, fighter.fighterId, skill.skill.skillBean.f_SkillID));
        moveToDirection(skill, skill.targetDirection);
    elseif (skillBean.f_SkillPoint == SkillTargetDirectingEnum.PLACE) then

        SkillRoleMgr.savePathFile(string.format("[%s][SKILL]%s的技能%s准备移动,目标点%s", fight.frame, fighter.fighterId, skill.skill.skillBean.f_SkillID, getJsonStr(skill.target.position)));
        arrive = moveToPoint(skill, skill.targetPosition, nil);
    elseif (skill.target ~= nil) then
        -- 目标已经死亡，停止移动，结束技能
        if (skill.target:isDie()) then
            this.finishSkill(skill, true, FinishSkillReasonEnum.TARGET_DIE);
            return ;
        end
        SkillRoleMgr.savePathFile(string.format("[%s][SKILL]%s的技能%s准备移动,目标点%s", fight.frame, fighter.fighterId, skill.skill.skillBean.f_SkillID, getJsonStr(skill.target.position)));
        arrive = moveToPoint(skill, skill.target.position, skill.target.shape);
    end

    -- 非分离技能把角色也一起移过去
    local outFighter = false;
    if (not skill.separate) then
        local oldPos = Vector2.New(fighter.position.x, fighter.position.y);
        outFighter = ClientFight.FighterManager.changePosition(fight, fighter, 0, skill.position.x, skill.position.y, 0, false, true);
        SkillRoleMgr.savePathFile(string.format("[%s][SKILL]%s的技能%s为非分离技能,角色移动到%s,原坐标%s", fight.frame, fighter.fighterId, skill.skill.skillBean.f_SkillID, fighter.position, oldPos));
    end

    local out = false;
    local skillIsOut = false; --技能释放的逻辑已经算是出界了
    -- 检查是否超出边界，非指向目标技能
    if (skill.separate) then
        -- 分离技能只考虑技能
        if (skill.position.x > Global.FIGHT_OUTER_ROUND[2][1] or skill.position.x < Global.FIGHT_OUTER_ROUND[1][1]) then
            out = true;
        end
        if (skill.position.y > Global.FIGHT_OUTER_ROUND[1][3] or skill.position.y < Global.FIGHT_OUTER_ROUND[2][3]) then
            out = true;
        end
    else
        if (skill.skill.skillBean.f_Out == 0) then
            out = ClientFight.FighterManager.checkOut(skill.fighter, Global.FIGHT_INNER_ROUND);
        elseif (skill.skill.skillBean.f_Out == 1) then
            out = ClientFight.FighterManager.checkOut(skill.fighter, Global.FIGHT_OUTER_ROUND);
        elseif (skill.skill.skillBean.f_Out == 2) then
            local ignoreLeft = skill.targetDirection == Global.RIGHT;
            out = ClientFight.FighterManager.checkOut(skill.fighter.position, skill.fighter.shape, Global.FIGHT_INNER_ROUND0, ignoreLeft, not ignoreLeft);
        elseif (skill.skill.skillBean.f_Out == 3) then
            --可以看技能表这个字段的解释
            local ignoreLeft = skill.targetDirection == Global.RIGHT;
            local vector2 = Vector2.New(skill.position.x,skill.position.y);--这里一定要new新的, 否则如果直接用skill的Position传入进去,那么技能的坐标会一直被修正,就不会往前冲了
            skillIsOut = FighterManager.checkOut(vector2, fighter.shape, Global.FIGHT_INNER_ROUND0, ignoreLeft, not ignoreLeft);
            out = FighterManager.checkOut(skill.fighter, Global.FIGHT_INNER_ROUND);
        end
        out = out and outFighter
        skill.position.x = skill.fighter.position.x;
        skill.position.y = skill.fighter.position.y;

    end
    if (out or skillIsOut) then
        skill.out = true;
    end
    SkillRoleMgr.savePathFile("[" .. fight.frame .. "][SKILL]" .. fighter.fighterId .. "的技能" .. skillBean.f_SkillID .. "移动到" .. getJsonStr(skill.position));

    -- 检查是否命中
    local hits = this.checkSkillHit(skill);
    if (hits ~= nil and table.getn(hits) > 0) then
        local ids = {};
        for i, _fighter in pairs(hits) do

            table.insert(ids, _fighter.fighterId);
        end
        SkillRoleMgr.savePathFile("[" .. fight.frame .. "][SKILL]" .. fighter.fighterId .. "的技能" .. skillBean.f_SkillID .. "移动到碰撞到" .. getJsonStr(ids));
        for i, v in pairs(hits) do
            skill.collideTargets[i] = v;
        end

        -- 这里是为了保证整个技能期间只对目标伤害一次
        if (skill.skill.skillBean.f_TargetRepeat) == SkillTargetRepeatEnum.UNREPEATABLE_ALL then
            local _skill = skill;
            while (_skill.parentSkill ~= nil and _skill.parentSkill ~= _skill) do
                _skill = _skill.parentSkill;
                for i, v in pairs(hits) do
                    _skill.collideTargets[i] = v;
                end ;
            end
        end

        if (skillBean.f_SkillHit == SkillHitTypeEnum.HIT_TARGET) then
            this.effectSkill(skill, this.HIT_TRIGGER);
        elseif (skillBean.f_SkillHit == SkillHitTypeEnum.HIT_ANY) then
            this.effectSkill(skill, this.HIT_TRIGGER);
        end
        for i, v in pairs(skill.SpineEffects) do
            v:stop()
        end
    end
    if (out and skillBean.f_SkillPoint == SkillTargetDirectingEnum.DIRECTION) then
        if (skill.frame >= 15) then
            SkillRoleMgr.savePathFile("[" + fight.frame + "][SKILL]" + fighter.fighterId + "的技能" + skillBean.f_SkillID + "因为出界（" + true + "） 而结束！");
            this.finishSkill(skill, false, FinishSkillReasonEnum.NORMAL_FINISH);
        end
    else
        -- 命中后结束
        if ((out or #hits > 0) and skillBean.f_SkillOver == SkillFinishTypeEnum.HIT_ANY) then
            local logOut = out;
            SkillRoleMgr.savePathFile("[" .. fight.frame .. "][SKILL]" .. fighter.fighterId .. "的技能" .. skillBean.f_SkillID .. "因为出界（" .. logOut .. "）或者命中（" .. tostring(#hits > 0) .. "）而结束！");
            this.finishSkill(skill, false, FinishSkillReasonEnum.NORMAL_FINISH);
        elseif (table.contains(hits, skill.target)) and skillBean.f_SkillOver == SkillFinishTypeEnum.HIT_TARGET then
            SkillRoleMgr.savePathFile("[" .. fight.frame .. "][SKILL]" .. fighter.fighterId .. "的技能" .. skillBean.f_SkillID .. "因为命中目标（" .. tostring(#hits > 0) .. "）而结束！");
            this.finishSkill(skill, false, FinishSkillReasonEnum.NORMAL_FINISH);
        elseif (arrive and skillBean.f_SkillOver == SkillFinishTypeEnum.ARRIVE) then
            SkillRoleMgr.savePathFile("[" .. fight.frame .. "][SKILL]" .. fighter.fighterId .. "的技能" .. skillBean.f_SkillID .. "因为到达地点(" .. getJsonStr(skill.position) .. ")而结束！");
            this.finishSkill(skill, false, FinishSkillReasonEnum.NORMAL_FINISH);
        end

    end

end

--[[
* 技能作用效果
]]
function this.effectSkill(skill, triggerFrame, skillUniqueId)
    if (skill.firstEffectPlacePosition == nil and skill.skill.skillBean.f_AreaTarget == TargetAreaCenterType.type8) then
        skill.firstEffectPlacePosition = Vector2.New(skill.target.position.x, skill.target.position.y);
        local frameInfo = ReportManager.lockSkillReleasPosition(skill.fight, skill.fighter,
                skill.target, skill.firstEffectPlacePosition, skill);
        table.insert(skill.fight.actions, frameInfo);
        FightManager.playActions(frameInfo)
    end
    -- 作用次数+1
    skill.effectTimes = skill.effectTimes + 1;
    local skillEffect = ClientFight.SkillClientManager.getSkillEffect(skill.skill.skillBean.f_SkillScript);
    if (skillEffect ~= nil) then
        if (skillEffect:effectSkill(skill, triggerFrame, skillUniqueId)) then
            return ;
        end
    end

    -- 单体目标验证一下是否死亡 范围伤害找一下溅射目标
    local targets = this.selectDamageTargets(skill, nil);
    -- 是否命中目标
    local hit = 0;
    for i, _target in pairs(targets) do

        table.insert(skill.actionTargets, _target);
        -- 这里是为了保证整个技能期间只对目标伤害一次
        if (skill.skill.skillBean.f_TargetRepeat == SkillTargetRepeatEnum.UNREPEATABLE_ALL) then
            local _skill = skill;
            while (_skill.parentSkill ~= nil and _skill.parentSkill ~= _skill) do
                _skill = _skill.parentSkill;
                table.insert(_skill.actionTargets, _target);
            end
        end
        local flag = DamageManager.attack(skill, _target, triggerFrame, skillUniqueId) and 1 or 0
        -- 攻击
        hit = bit.bor(hit, flag);
    end

    if (hit == 1) then
        -- 技能触发技能
        if (skill.triggerFrames[this.HIT_TRIGGER]) then
            local triggerSkills = skill.triggerFrames[this.HIT_TRIGGER];
            for i, tSkill in pairs(triggerSkills) do

                this.triggerSkill(skill, nil, tSkill);
            end
        end
        checkAndCreateSummon(this.HIT_TRIGGER, skill);
    end
end

--[[
* 向左或右移动
]]
function this.moveToDirection(skill, direction)
    skill.position.x = skill.position.x + direction * skill.moveSpeed;
end

--[[
* 向目标坐标移动
*
* @param position 目标坐标点
]]
function this.moveToPoint(skill, position, shape)
    targetPosition = position;
    -- 非分离技能（冲锋类技能）
    if (not skill.separate and shape ~= nil) then
        -- 计算冲锋点
        targetPosition = moveToPosition(skill.position, skill.skillShape, position, shape);
    end
    -- 开始移动
    local distance = DistanceUtil.countDistance(skill.position, targetPosition);

    if (skill.moveSpeed ~= 0) then
        local moved = skill.moveSpeed;

        if (distance <= moved) then
            skill.position.x = targetPosition.x;
            skill.position.y = targetPosition.y;
            return true;
        end

        local x = targetPosition.x - skill.position.x;
        local y = targetPosition.y - skill.position.y;
        skill.position.x = skill.position.x + (x * moved / distance);
        skill.position.y = skill.position.y + (y * moved / distance);
    elseif (skill.moveFrame > 0) then
        -- 在指定帧数时移动到目标点
        local frame = skill.moveFrame;
        skill.moveFrame = frame - 1;
        if (skill.moveFrame == 0) then
            skill.position.x = targetPosition.x;
            skill.position.y = targetPosition.y;
            return true;
        end

        local moved = distance / frame;
        local x = targetPosition.x - skill.position.x;
        local y = targetPosition.y - skill.position.y;
        skill.position.x = (skill.position.x + (x * moved / distance));
        skill.position.y = (skill.position.y + (y * moved / distance));
    end
    return false;
end

--[[
* 计算跑向位置
]]
function this.moveToPosition(position1, shape1, position2, shape2)
    local x = nil;
    local leftOrRight = position1.x < position2.x and -1 or 1;
    -- 横向在攻击距离之外， 需要横向跑动
    if (math.abs(position1.x - position2.x) * 2 > (shape1).length + shape2.length) then
        x = position2.x + leftOrRight * (shape1.length + shape2.length) / 2;
    else
        x = position1.x;
    end

    local y = nil;
    local upOrDown = position1.y < position2.y and -1 or 1;
    -- 横向在攻击距离之外， 需要横向跑动
    if (math.abs(position1.y - position2.y) * 2 > shape1.width + shape2.width) then
        y = position2.y + upOrDown * (shape1.width + shape2.width) / 2;
    else
        y = position1.y;
    end

    return Vector2.New(x, y);
end

--[[
* 检查技能是否命中
]]
function this.checkSkillHit(skill)
    local fight = skill.fight;
    local target = skill.target;
    local skillBean = skill.skill.skillBean;

    local hits = {};

    -- 按时间命中这里不考虑
    if (skillBean.f_SkillHit == SkillHitTypeEnum.TIME) then
        return hits;
    end

    -- 命中指定目标
    if (skillBean.f_SkillHit == SkillHitTypeEnum.HIT_TARGET) then
        if (DistanceUtil.overlap(skill.position, skill.skillShape, target.position, target.shape)) then
            table.insert(hits, target);
            return hits;
        end
    end

    -- 命中任何目标
    if (skillBean.f_SkillHit == SkillHitTypeEnum.HIT_ANY) then
        local fighters = fight.teams[target.teamIndex].fighters;
        for i, _fighter in pairs(fighters) do
            if (_fighter:isDie()) then
                goto continue
            end
            if (FighterStateEnum.is(_fighter, FighterStateEnum.CAN_NOT_SELECT_BY_ENEMY)) then
                goto continue
            end
            -- 技能只作用一次
            if (skillBean.f_TargetRepeat == SkillTargetRepeatEnum.UNREPEATABLE) then
                if table.contains(skill.collideTargets, _fighter) then
                    goto continue
                end
            end

            if (skillBean.f_TargetRepeat == SkillTargetRepeatEnum.UNREPEATABLE_ALL) then
                if table.contains(skill.collideTargets, _fighter) then
                    goto continue
                end
                local find = false;
                local _skill = skill;
                while (_skill.parentSkill ~= nil and _skill.parentSkill ~= _skill) do
                    _skill = _skill.parentSkill;
                    if table.contains(_skill.collideTargets, _fighter) then
                        find = true;
                        break ;
                    end
                end
                if (find) then
                    goto continue
                end
            end
            if (DistanceUtil.overlap(skill.position, skill.skillShape, _fighter.position, _fighter.shape)) then
                table.insert(hits, _fighter);
            end
            :: continue ::
        end
    end

    return hits;
end


--[[
* 每帧执行技能
]]
function this.frame(skill)
    -- 技能释放者死亡
    if (skill.fighter:isDie()) then
        this.finishSkill(skill, true, FinishSkillReasonEnum.ATTACKER_DIE);
        return ;
    end
    if (skill.skill.skillBean.f_dieContinue == SkillContinueTypeEnum.STOP_ON_TARGETDIE) then
        local finish = false;
        local target = skill.target;
        if (target ~= nil) then
            if (target:isDie()) then
                finish = true;--目标死了,直接结束技能
            else
                if (target.heroBean.f_HeroID == 610) then
                    if table.contains(target.totalSign.signs, FighterSignEnum.TAG_998) then
                        finish = true; --如果是西泽, 西泽在死亡复活阶段, 也直接结束技能
                    end
                end
            end
        end
        if (finish) then
            this.finishSkill(skill, false, FinishSkillReasonEnum.TARGET_DIE);
            return ;

        end
    end
    -- 技能已经结束
    if (skill.stage == FightSkillStageEnum.FINISH) then
        return ;
    end

    -- 增加技能帧数
    skill.frame = skill.frame + 1;
    -- 技能移动
    if (skill.moveSpeed ~= 0 or skill.moveFrame > 0) then
        --不使用帧事件则数据计算是否命中
        moveSkill(skill);
    end

    -- 技能增加buff
    if (skill.triggerBuffFrames[skill.frame]) then
        local buffs = skill.triggerBuffFrames[skill.frame];
        for key, entry in pairs(buffs) do

            ClientFight.BuffManager.addBuff(skill.fight, skill, skill.fighter, skill.target, false, key, entry, skill.target, skill);
        end
    end

    -- 技能触发技能
    if skill.triggerFrames[skill.frame] then
        local triggerSkills = skill.triggerFrames[skill.frame];
        for i, tSkill in pairs(triggerSkills) do
            local skillEffect = SkillManager.getSkillEffect(skill.skill.skillBean.f_SkillScript);
            if (skillEffect ~= nil) then
                if (skillEffect.overrideTriggerSkill(skill, nil, triggerSkill)) then
                    goto continue;
                end
            end
            this.triggerSkill(skill, nil, tSkill);
            :: continue ::
        end
    end

    -- 召唤物
    checkAndCreateSummon(skill.frame, skill);

    -- 技能发生作用
    if (skill.actionFrames[skill.frame]) then
        if skill.skill.skillId == 63400 then
            local a = 1;
        end
        this.effectSkill(skill, skill.frame);
    end

    -- 聚光灯播放效果
    if (skill.frame >= skill.skipFrame and skill.skipFrame > 0) then
        table.ArrayRemoveByValue(skill.fight.frozenSkills, skill)
        SkillRoleMgr.savePathFile(skill. string.format("[%s][SKILL]%s黑屏时间结束", skill.fight.frame, skill.skill.skillBean.f_SkillID));
    end

    -- 是否进入下一阶段
    if (skill.frame < skill.stageFinishFrame) then
        return ;
    end
    if skill.stage == FightSkillStageEnum.PREPARE then
        this.releaseSkill(skill);
    end
    if skill.stage == FightSkillStageEnum. GUIDE then
        this.finishSkill(skill, false, FinishSkillReasonEnum.NORMAL_FINISH);
    end

end

--[[
* 选择攻击目标
*
* @param excludes 排除目标<fightId>
]]
addOverload(this, "selectAttackTarget", function(fight, fighter, skill, center, excludes,
                                                 fightRandom)
    return selectAttackTarget(fight, fighter, skill, center, excludes, fightRandom, false);
end, "table", "table", "table", "table", "table", "boolean")

--[[
* 选择攻击目标
*
* @param excludes 排除目标<fightId>
]]
addOverload(this, "selectAttackTarget", function(fight, fighter, skill, center, excludes,
                                                 fightRandom, usePlayerAi)
    local skillBean = skill.skillBean;

    -- 吸引普攻子弹，忽略攻击距离
    if (skillBean.f_SkillType == 1 and skillBean.f_GeneralAttackType == 1) then
        local others = FighterUtils.getOthersBy(fighter, true, nil);
        for i, other in pairs(others) do

            if (FighterStateEnum.is(other, FighterStateEnum.ATTRACT_NORMAL_BULLET)) then
                return other;
            end
        end
    end

    local skillEffect = getSkillEffect(skillBean.f_SkillScript);
    if (skillEffect ~= nil) then
        local target = skillEffect:selectAttackTarget(fight, fighter, skill, center, excludes);
        if (target ~= nil) then
            if (target == ClientFight.FighterManager.emptyFighter) then
                return nil;
            end
            return target;
        end
    end

    return this.selectAttackTarget(fight, fighter, skill, skillBean.f_TargetRange, skillBean.f_Target, skill:getTargetRule(),
            AttackUtils.getAttackRadius(fighter, skill), AttackUtils.getAttackShape(fighter, skill), center, skillBean.f_TagActivate, excludes, fightRandom, usePlayerAi);
end, "table", "table", "table", "table", "table", "boolean", "boolean")

--[[
* 选择攻击目标
*
* @param excludes    排除目标
* @param targetRange 攻击范围 {@link SkillTargetRangeEnum}
* @param targetRule  目标选择规则{@link SkillTargetRuleEnum}
* @param targetType  敌我或自己 {@link SkillTargetTypeEnum}
* @param tagActivate buff标记 拥有这个标记的人纳入选择范围内
* @param attackShape 攻击范围形状 (矩形)
]]
addOverload(this, "selectAttackTarget", function(fight, fighter, skill, targetRange, targetType, targetRule,
                                                 radius, attackShape, center, tagActivate, excludes,
                                                 fightRandom)

    return this.selectAttackTarget(fight, fighter, skill, targetRange, targetType, targetRule,
            radius, attackShape, center, tagActivate, excludes, fightRandom, false);
end, "table", "table", "table", "number", "number", "number", "number", "table", "table", "table", "table", "boolean")
addOverload(this, "selectAttackTarget", function(fight, fighter, skill, targetRange, targetType, targetRule,
                                                 radius, attackShape, center, tagActivate, excludes,
                                                 fightRandom, usePlayerAi)
    local skillBean = skill.skillBean;
    local targetFighters = this.selectAttackTargets(fight, fighter, skill,
            targetRange, targetType, radius, attackShape, center, tagActivate, excludes, usePlayerAi);

    if (table.getn(targetFighters) == 0) then
        SkillRoleMgr.savePathFile("[" .. fight.frame .. "][目标选择]" .. fighter:print() .. "技能" .. skillBean.f_SkillID .. "可供选择的目标为空");
        return nil;
    end

    if table.getn(targetFighters) == 1 then
        local _fighter = targetFighters[1];
        SkillRoleMgr.savePathFile("[" .. fight.frame .. "][目标选择]" .. fighter:print() .. "技能" .. skillBean.f_SkillID .. "选择的唯一目标为" .. _fighter.fighterId);
        return _fighter;
    end
    for i, v in pairs(targetFighters) do

        -- 移除死亡
        if (v:isDie()) then
            targetFighters[i] = nil;
            goto continue
        end
        -- 移除召唤物
        if (skillBean.f_AimTarget == 1 and v.summons) then
            targetFighters[i] = nil;
            goto continue
        end
        -- 移除英雄
        if (skillBean.f_AimTarget == 2 and not v.summons) then
            targetFighters[i] = nil;
            goto continue
        end
        if (skillBean.f_AimTarget == 3) then
            if (v.summons or v.heroBean.f_Type == 3) then
                targetFighters[i] = nil;
                goto continue
            end
        end
        :: continue ::

    end
    if (table.getn(targetFighters) == 0) then
        return nil;
    end

    local selectedFighters = ClientFight.TargetRuleSelector:action(fighter, targetRule, targetFighters, skill:getTargetRuleParam(fighter));
    if (table.getn(selectedFighters) > 0) then
        return selectedFighters[1];
    end
    if (targetRule == ClientFight.TargetRuleSelector.selector0.id) then
        return nil;
    end
    selectedFighters = ClientFight.TargetRuleSelector:action(fighter, TargetRuleSelector.selector0.id,targetFighters    , skill.targetRuleParam);
    return table.getn(selectedFighters) == 0 and nil or selectedFighters[1];
end, "table", "table", "table", "number", "number", "number", "number", "table", "table", "table", "table", "boolean", "boolean")


--[[
* 选择可以攻击的目标列表
*
* @param fighter     攻击者
* @param targetRange 攻击范围 {@link SkillTargetRangeEnum}
* @param targetType  敌我或自己 {@link SkillTargetTypeEnum}
* @param radius      半径
* @param attackShape 攻击范围形状 (矩形)
* @param tagActivate buff标记 拥有这个标记的人纳入选择范围内
* @param excludes    排除
]]
function this.selectAttackTargets(fight, fighter, skill, targetRange, targetType, radius,
                                  attackShape, center, tagActivate, excludes, usePlayerAi)
    usePlayerAi = not not usePlayerAi;
    local skillId = skill.skillId;
    local targetFighters = {};
    -- 技能作用目标为自己
    if (targetType == SkillTargetTypeEnum.ONESELF) then
        table.insert(targetFighters, fighter);
        SkillRoleMgr.savePathFile("[" .. fight.frame .. "][目标选择]" .. fighter:print() .. "技能" .. skillId .. "选择自己为目标");
        return targetFighters;
    end

    local skillBean = skill.skillBean;
    -- 嘲讽
    if (fighter.sneerTarget ~= nil and skillBean.f_Target == SkillTargetTypeEnum.TARGET) then
        if (fighter.sneerTarget:isDie()) then
            fighter.sneerTarget = nil;
            SkillRoleMgr.savePathFile(string.format("[%s][目标选择]%s因为嘲讽目标死亡执行正常目标选择流程", fight.frame, fighter:print()));
        else
            table.insert(targetFighters, fighter.sneerTarget);
            SkillRoleMgr.savePathFile(string.format("[%s][目标选择]%s因为有嘲讽目标加入选择列表中", fight.frame, fighter:print()));
            return targetFighters;
        end
    end

    -- XXX 这里要考虑混乱攻击自己人的情况
    -- 魅惑
    if (FighterStateEnum.is(fighter, FighterStateEnum.CHARM)) then
        if (targetType == SkillTargetTypeEnum.FRIEND or targetType == SkillTargetTypeEnum.FRIEND_AND_ONESELF) then
            addTable(targetFighters, fight.teams[3 - fighter.teamIndex].fighters);
        else
            if (targetType == SkillTargetTypeEnum.TARGET) then
                addTable(targetFighters, fight.teams[fighter.teamIndex].fighters);
            end
        end
        -- 目标为友方
    elseif (targetType == SkillTargetTypeEnum.FRIEND or targetType == SkillTargetTypeEnum.FRIEND_AND_ONESELF) then
        addTable(targetFighters, fight.teams[fighter.teamIndex].fighters);
    elseif (targetType == SkillTargetTypeEnum.TARGET) then
        local fighters = fight.teams[3 - fighter.teamIndex].fighters;
        for i = 1, table.getn(fighters) do
            local _fighter = fighters[i];
            if not skill.fighterSelectStates[_fighter.fighterId] then
                skill.fighterSelectStates[_fighter.fighterId] = false;
                local fightResult = FightResult.New();
                ClientFight.EventManager.notifyEventListener(fight, FightEventEnum.BE_SELECT, _fighter.fighterId, fightResult);
                if (fightResult.state == DamageResultTypeEnum.IMMUNITY) then
                    --被免疫掉了本次被选中
                    skill.fighterSelectStates[_fighter.fighterId] = true;
                    SkillRoleMgr.savePathFile("[" .. fight.frame .. "][SKILL]" .. fighter.fighterId .. "的" .. skillId .. "选择攻击目标" .. _fighter.fighterId .. "因为免疫本次选中而排除");
                    goto continue
                end
            end
            table.insert(targetFighters, _fighter);
            :: continue ::
        end
    end

    if (table.getn(targetFighters) <= 0) then
        SkillRoleMgr.savePathFile("[" .. fight.frame .. "][目标选择]" .. fighter:print() .. "技能" .. skillId .. "目标指向错误");
        return targetFighters;
    end
    if (skill:getTargetRule() == ClientFight.TargetRuleSelector.selector38.id) then
        targetFighters = selectFightersInAttackRange(fight, skill, targetType, fighter, targetFighters,
                center, radius, attackShape, tagActivate, excludes, false, usePlayerAi);
    else
        -- 技能目标范围内选择
        if (targetRange == SkillTargetRangeEnum.IN_ATTACK_RANGE) then
            -- 技能目标范围内选择
            targetFighters = selectFightersInAttackRange(fight, skill, targetType, fighter, targetFighters,
                    center, radius, attackShape, tagActivate, excludes, true, usePlayerAi);
            -- 先在攻击范围内，选择不到的时候在全部
        elseif (targetRange == SkillTargetRangeEnum.IN_ATTACK_RANGE_OTHER_ALL) then
            local selectFighters = selectFightersInAttackRange(fight, skill, targetType, fighter, targetFighters,
                    center, radius, attackShape, tagActivate, excludes, true, usePlayerAi);

            if (table.getn(selectFighters) == 0) then
                targetFighters = selectFightersInAttackRange(fight, skill, targetType, fighter, targetFighters, center,
                        radius, attackShape, tagActivate, excludes, false, usePlayerAi);
            else
                targetFighters = selectFighters;
            end
        else
            targetFighters = selectFightersInAttackRange(fight, skill, targetType, fighter, targetFighters,
                    center, radius, attackShape, tagActivate, excludes, false, usePlayerAi);
        end
    end
    if (table.getn(targetFighters) == 0) then
        SkillRoleMgr.savePathFile("[" .. fight.frame .. "][目标选择]" .. fighter:print() .. "技能" .. skillId .. "可供选择的目标为空");
        return targetFighters;
    end

    return targetFighters;
end


--[[
* 选择攻击范围内的目标
*
* @param rec        矩形范围
* @param signs      buff标记 拥有这个标记的人纳入选择范围内
* @param inRange    是否需要判断攻击范围
]]
function this.selectFightersInAttackRange(fight, skill, targetType, fighter, fighters,
                                          center, radius, rec, signs, excludes,
                                          inRange, usePlayerAi)

    local result = {};
    local resultDefault = {};
    local skillBean = skill.skillBean;
    for i, _fighter in pairs(fighters) do

        if (_fighter:isDie()) then
            goto continue
        end
        -- 移除召唤物
        if (skillBean.f_AimTarget == 1 and _fighter.summons) then
            goto continue
        end
        -- 移除英雄
        if (skillBean.f_AimTarget == 2 and not _fighter.summons) then
            goto continue
        end
        if (skillBean.f_AimTarget == 3) then
            if (_fighter.summons or _fighter.heroBean.f_Type == 3) then
                goto continue
            end
        end
        -- 不能选自己
        if (targetType ~= SkillTargetTypeEnum.FRIEND_AND_ONESELF and fighter.fighterId == _fighter.fighterId) then
            goto continue;
        end

        -- 不可被普攻选中
        if (FighterStateEnum.is(_fighter, FighterStateEnum.CAN_NOT_SELECT_BY_NORMAL_ATTACK) and skillBean.f_SkillType == SkillTypeEnum.NORMAL_ATTACK) then
            SkillRoleMgr.savePathFile("[" .. fight.frame .. "][SKILL]" .. fighter.fighterId .. "的" .. skillBean.f_SkillID .. "选择伤害目标" .. _fighter.fighterId .. "因为不可被敌方 普攻伤害 而排除");
            goto continue;
        end

        if (targetType == SkillTargetTypeEnum.FRIEND and FighterStateEnum.is(_fighter, FighterStateEnum.CAN_NOT_SELECT_BY_FRIENDLY)) then
            goto continue
        elseif (targetType == SkillTargetTypeEnum.TARGET and FighterStateEnum.is(_fighter, FighterStateEnum.CAN_NOT_SELECT_BY_ENEMY)) then
            goto continue
        elseif (targetType == SkillTargetTypeEnum.FRIEND_AND_ONESELF) then
            if (_fighter ~= fighter) then
                if (FighterStateEnum.is(_fighter, FighterStateEnum.CAN_NOT_SELECT_BY_FRIENDLY)) then
                    goto continue
                end
            end
        end

        -- 标记选择
        if (signs ~= nil) then
            if (not skillTargetSignCheck(_fighter, signs)) then
                goto continue
            end
        end

        if (excludes ~= nil and table.contains(excludes, _fighter.fighterId)) then
            goto continue
        end

        if (inRange) then
            -- 在攻击范围外
            if (rec ~= nil) then
                if (not DistanceUtil.overlap(center, rec, _fighter.position, _fighter.shape)) then
                    goto continue
                end
            else
                if (not DistanceUtil.overlap(center, radius, _fighter.position)) then
                    goto continue
                end
            end
        end

        local skillType = skill.skillType;
        if (skillType > 0 and usePlayerAi and (signs == nil or table.getn(signs) < 1)) then
            --带标记的技能,表示是强制选择, 无法被技能编辑ai改变规则
            --所以这个if里判断的就是 不带标记的技能, 才需要走自定义ai
            local heroSkillAiBean = fighter.fightTeam.heroSkillAi[fighter.modelId];
            if (heroSkillAiBean ~= nil) then
                local skillAiBean = heroSkillAiBean.skillTypeMap[skillType];
                if (skillAiBean ~= nil) then
                    local aiSelect = skillAiBean.aiSelect(fighter, _fighter, skill);
                    if (aiSelect ~= 0) then
                        --不符合玩家设定的选择目标策略
                        if (aiSelect == 1) then
                            resultDefault.add(_fighter);
                        end
                        goto continue;
                    end
                end
            end
        end

        SkillRoleMgr.savePathFile(string.format("[%s][SKILL][%s]%s的%s选择目标%s成功", fight.frame, fighter.fighterId, fighter.fighterId,
                skill.skillId, _fighter.fighterId));

        table.insert(result, _fighter);
        :: continue ::
    end
    if (table.getn(result) == 0) then
        return resultDefault;
    end
    return result;
end

function this.skillTargetSignCheck(target, signs)

    for i, sign in pairs(signs) do

        local contains = target.totalSign.signs[math.abs(sign)];
        if (sign > 0 and not contains) then
            return false;
        end
        if (sign < 0 and contains) then
            return false;
        end
    end
    return true;
end

--[[
* 选择技能范围内目标 单体伤害只是验一下死亡 范围伤害找一下周围的溅射目标
]]
function this.selectDamageTargets(skill, center)
    local fight = skill.fight;
    local fighter = skill.fighter;
    local target = skill.target;
    local skillBean = skill.skill.skillBean;
    local skillId = skillBean.f_SkillID;

    -- 单体伤害
    if (skillBean.f_AreaShape == SkillTargetAreaShapeEnum.SINGLE) then
        if (not target:isDie()) then
            if (skillBean.f_TargetRepeat == SkillTargetRepeatEnum.UNREPEATABLE) then
                if table.contains(skill.actionTargets, target) then
                    SkillRoleMgr.savePathFile("[" .. fight.frame .. "][SKILL]" .. fighter.fighterId .. "的" .. skillId .. "选择伤害目标" .. target.fighterId .. "因为被伤害过而排除");
                    return {};
                end
            end
            local targets = {};
            table.insert(targets, target);
            return targets;
        end
        -- 矩形伤害
    elseif (skillBean.f_AreaShape == SkillTargetAreaShapeEnum.RECTANGLE) then
        -- 中心点
        if (center == nil) then
            center = TargetAreaCenterType:valueOf(skillBean.f_AreaShape):action(skill);
        end
        local logCenter = center;
        local enemyList = fight.teams[target.teamIndex].fighters;
        local selectors = {};
        SkillRoleMgr.savePathFile("[" .. fight.frame .. "][SKILL]" .. fighter.fighterId .. "准备对" .. target.fighterId .. "使用技能" .. skillBean.f_SkillID .. "进行伤害选择，中心点为" .. getJsonStr(logCenter));
        for i = 1, table.getn(enemyList) do
        local enemy = enemyList[i];
            -- 移除死亡
            if (enemy:isDie()) then
                goto continue
            end
            -- 移除召唤物
            if (skillBean.f_AimTarget == 1 and enemy.summons) then
                goto continue
            end
            -- 移除英雄
            if (skillBean.f_AimTarget == 2 and not enemy.summons) then
                goto continue
            end
            if (skillBean.f_AimTarget == 3) then
                if (enemy.summons or enemy.heroBean.f_Type == 3) then
                    goto continue
                end
            end
            if (skillBean.f_Target == SkillTargetTypeEnum.TARGET) then
                if (FighterStateEnum.is(enemy, FighterStateEnum.CAN_NOT_DAMAGE_BY_ENEMY)) then
                    SkillRoleMgr.savePathFile("[" .. fight.frame .. "][SKILL]" .. fighter.fighterId .. "的" .. skillId .. "选择伤害目标" .. enemy.fighterId .. "因为不可被敌方伤害而排除");
                    goto continue
                    end
                    -- 不可被普攻选中
                    if (FighterStateEnum.is(enemy, FighterStateEnum.CAN_NOT_SELECT_BY_NORMAL_ATTACK) and skill.skill.skillBean.f_SkillType == SkillTypeEnum.NORMAL_ATTACK) then
                        SkillRoleMgr.savePathFile( "[" .. fight.frame .. "][SKILL]" .. fighter.fighterId .. "的" .. skillId .. "选择伤害目标" .. enemy.fighterId ..  "因为不可被敌方 普攻伤害 而排除");
                    goto continue;
                end
                local abs = skill.skill.fighterSelectStates[enemy.fighterId];
                if (abs == nil) then
                    skill.skill.fighterSelectStates[enemy.fighterId] = false;
                    local fightResult = FightResult.New();
                    ClientFight.EventManager.notifyEventListener(fight, FightEventEnum.BE_SELECT, enemy.fighterId, fightResult);
                    if (fightResult.state == DamageResultTypeEnum.IMMUNITY) then
                        --被免疫掉了本次被选中
                        skill.skill.fighterSelectStates[enemy.fighterId] = true;
                        goto continue
                    end
                elseif (abs == true) then
                    SkillRoleMgr.savePathFile("[" .. fight.frame .. "][SKILL]" .. fighter.fighterId .. "的" .. skillId .. "选择伤害目标" .. enemy.fighterId .. "因为免疫本次选中而排除");
                    goto continue
                end
            end

            if (skillBean.f_Target == SkillTargetTypeEnum.FRIEND) then
                if (FighterStateEnum.is(enemy, FighterStateEnum.CAN_NOT_DAMAGE_BY_FRIENDLY) or enemy.fighterId == fighter.fighterId) then
                    SkillRoleMgr.savePathFile("[" .. fight.frame .. "][SKILL]" .. fighter.fighterId .. "的" .. skillId .. "选择伤害目标" .. enemy.fighterId .. "因为不可包含自己而排除/不可被友方伤害而排除");
                    goto continue
                end
            elseif (skillBean.f_Target == SkillTargetTypeEnum.FRIEND_AND_ONESELF) then
                if (enemy ~= fighter) then
                    if (FighterStateEnum.is(enemy, FighterStateEnum.CAN_NOT_DAMAGE_BY_FRIENDLY)) then
                        SkillRoleMgr.savePathFile("[" .. fight.frame .. "][SKILL]" .. fighter.fighterId .. "的" .. skillId .. "选择伤害目标" .. enemy.fighterId .. "因为不可被友方伤害而排除");
                        goto continue
                    end
                end
            end

            if (skillBean.f_TargetRepeat == SkillTargetRepeatEnum.UNREPEATABLE) then
                if skill.actionTargets[enemy] then
                    SkillRoleMgr.savePathFile("[" .. fight.frame .. "][SKILL]" .. fighter.fighterId .. "的" .. skillId .. "选择伤害目标" .. enemy.fighterId .. "因为被伤害过而排除");
                    goto continue
                end
            end

            if (skillBean.f_TargetRepeat == SkillTargetRepeatEnum.UNREPEATABLE_ALL) then
                if (skill.actionTargets[enemy]) then
                    SkillRoleMgr.savePathFile("[" .. fight.frame .. "][SKILL]" .. fighter.fighterId .. "的" .. skillId .. "选择伤害目标" .. enemy.fighterId .. "因为被伤害过而排除");
                    goto continue
                end
                local rootSkill = getRootSkill(skill);
                local attackedTargets = rootSkill.attackedTargets;
                if (attackedTargets[enemy.fighterId]) then
                    SkillRoleMgr.savePathFile("[" .. fight.frame .. "][SKILL]" .. fighter.fighterId .. "的" .. skillId .. "选择伤害目标" .. enemy.fighterId .. "因为被伤害过而排除");
                    goto continue
                end
            end

            -- 标记选择
            local signs = skillBean.f_TagActivate;
            if (signs ~= nil) then
                if (not skillTargetSignCheck(enemy, signs)) then
                    SkillRoleMgr.savePathFile("[" .. fight.frame .. "][SKILL]" .. fighter.fighterId .. "的" .. skillId .. "选择伤害目标" .. enemy.fighterId .. "因为没有标记而排除");
                    goto continue
                end
            end

            -- 距离计算
            if (DistanceUtil.overlap(center, skill.skill.damageShape, enemy.position, enemy.shape)) then
                SkillRoleMgr.savePathFile("[" .. fight.frame .. "][SKILL]" .. fighter.fighterId .. "的" .. skillId .. "选择伤害目标" .. enemy.fighterId .. "坐标" .. getJsonStr(enemy.position) .. "成功");
                table.insert(selectors, enemy);
            else
                SkillRoleMgr.savePathFile(string.format("[%s][SKILL]%s的%s选择伤害目标%s坐标:%s形状:%s,因为在伤害范围外而排除,技能中心点%s,形状%s", fight.frame, fighter.fighterId, skillId, enemy.fighterId, enemy.position, enemy.shape, logCenter, skill.skill.damageShape));
            end
            :: continue ::
        end

        if (table.getn(selectors) == 0) then
            return selectors;
        end

        local selected = ClientFight.TargetRuleSelector:action(fighter, skill.skill:getTargetRule(), selectors, skill.skill.targetRuleParam);

        local skillEffect = ClientFight.SkillClientManager.getSkillEffect(skill.skill.skillBean.f_SkillScript);
        local targetMax = skillBean.f_TargetMax;
        if (skillEffect ~= nil) then
            targetMax = skillEffect:getTargetMax(skill);
        end

        --            if (skillBean.f_TargetRepeat == SkillTargetRepeatEnum.UNREPEATABLE_ONCE.getId() or
        --                    skillBean.f_TargetRepeat == SkillTargetRepeatEnum.UNREPEATABLE.getId() or
        --                    skillBean.f_TargetRepeat == SkillTargetRepeatEnum.UNREPEATABLE_ALL.getId()) {
        --
        ----                while (targets.size() < targetMax and !selected.isEmpty()) {
        ----                    Fighter randomSelect = selected.remove(fight:random(selected.size()));
        ----                    targets.add(randomSelect);
        ----                }
        --                return randomNopeatabeTarget(selected, targetMax, fight);
        --            } else if (skillBean.f_TargetRepeat == SkillTargetRepeatEnum.REPEATABLE.getId()) {
        --                List<Fighter> targets = new ArrayList<>(targetMax);
        --                while (targets.size() < targetMax and !selected.isEmpty()) {
        --                    targets.add(selected.get(fight:random(selected.size())));
        --                }
        --                return targets;
        --            }
        return subList(selected, 1, math.min(targetMax, table.getn(selected)));
    end
    return {};
end

--[[
* 随机取 N个不重复的攻击目标
* 这个方法仅仅用于战斗随机取攻击目标
*
* 需要用到随机list不重复的元素 请使用 {@link RandomUtils#randomList(List, int)}
]]
function this.randomNopeatabeTarget(
        source, num, fight)
    -- 没有源或要取的数小于1个就直接返回空列表
    if (source == nil or num < 1) then
        return {};
    end

    -- 数量刚刚好
    if (table.getn(source) <= num) then
        return source;
    end

    -- 随机交换元素
    local i = 1;
    while i <= table.getn(source) do
        if (num <= 0) then
            --随机的数量够了, 把多余的给删除了
            table.remove(table.getn(source) - 1);
            i = i - 1;
            goto continue
        end
        local index = fight:random(table.getn(source) - i - 1) + i;
        if (index == i) then
            goto continue
        end
        local cur = source[i];
        local ran = source[index];
        source[i] = ran;
        source[index] = cur;

        i = i + 1;
        num = num - 1;
        :: continue ::
    end
    return source;
end

--[[
* 增加技能
* @return 返回技能所需能量值
]]
addOverload(this, "addSkill", function(fight, fighter, skillId, skillType)
    local fskill = createFightSkill(fight, skillId, fighter);
    if (fskill == nil) then
        return nil;
    end
    fskill.skillType = skillType;
    local skillBean = fskill.skillBean;
    if (skillBean.f_TriggerType == SkillActionTypeEnum.ACTIVE or
            skillBean.f_TriggerType == SkillActionTypeEnum.TRIGGER) then

        table.insert(fighter.skills, fskill);
        if (skillBean.f_CdIni > 0) then
            -- 进场CD
            local cd = countSkillPreCD(fighter, skillBean);
            local originalCd = countOriginalSkillPreCD(fighter, skillBean);
            addCooldown(fighter, skillBean.f_SkillID, cd / Global.FIGHT_FRAME, originalCd / Global.FIGHT_FRAME);
        end
    else
        table.insert(fighter.passiveskills, fskill);
    end
    --[[添加替补]]
    if not string.IsEmpty(skillBean.f_SummonThing1) then
        --技能有替补

    end

    return fskill;
end, "table", "table", "number", "number")

--[[
* 转化为专武升级后的技能id
* @param fighter
* @param srcSkillId
* @return
]]
function this.transverterArmSkill(fighter, srcSkillId)
    if (fighter == nil) then
        return srcSkillId;
    end
    if (true) then
        --            return srcSkillId;
    end
    local armskillBean = ClientFight.DataManager.armskillUnlockMap[srcSkillId];
    if (armskillBean == nil) then
        return srcSkillId;
    end
    if (not armskillBean.isUnlock(fighter.weaponLevel)) then
        return srcSkillId;
    end
    local dynamicParam = armskillBean.getDynamicParam(fighter.weaponLevel);
    local dyMap = {};
    dyMap[DynamicParam.DMP] = dynamicParam .. "";
    fighter.skillDynamicParam[armskillBean.f_ArmSkill] = dyMap;
    return armskillBean.armSkill;
end

--[[
* 增加被动技能效果
]]
function this.addPassiveSkillEffect(fight, source, fighter, usingSkill)
    local skillBean = usingSkill.skill.skillBean;

    if (skillBean.f_BuffBySkill ~= nil) then
        local team = fight.teams[source.teamIndex];
        local initFighters = nil;
        if team.initSkillFighters[usingSkill.skillUniqueId] then
            initFighters = team.initSkillFighters[usingSkill.skillUniqueId];
        else
            initFighters = {};
            team.initSkillFighters[usingSkill.skillUniqueId] = initFighters;
        end
        -- 已经初始化过
        if (not initFighters[source.fighterId]) then
            initFighters[source.fighterId] = true;
            for i = 1, table.getn(skillBean.f_BuffBySkill) do
                --被动技能,配到这个字段上, 是强行给自己加的
                local buffInfo = skillBean.f_BuffBySkill[i];
                ClientFight.BuffManager.addBuff(fight, usingSkill, source, source, true, buffInfo[2], buffInfo[3], fighter, usingSkill);
            end
        end
    end
    if (skillBean.f_DelayTimeByBuff ~= nil) then
        local team = fight.teams[fighter.teamIndex];
        local initFighters = nil;
        if team.initSkillFighters[usingSkill.skillUniqueId] then
            initFighters = team.initSkillFighters[usingSkill.skillUniqueId];
        else
            initFighters = {};
            team.initSkillFighters[usingSkill.skillUniqueId] = initFighters;
        end
        -- 已经初始化过
        if not initFighters[fighter.fighterId] then
            initFighters[fighter.fighterId] = true;
            for i, oneAction in pairs(skillBean.f_DelayTimeByBuff) do

                ClientFight.BuffManager.addBuff(fight, usingSkill, source, fighter, true, oneAction[2], oneAction[3], fighter, usingSkill);
            end
        end
    end
end

--[[
* 添加冷却
*
* @param fighter 战士
* @param skillId 技能ID
* @param delay   冷却时间
]]
function this.addCooldown(fighter, skillId, delay, originalDelay)
    if (delay <= 0) then
        return ;
    end
    local cooldown = fighter.cooldowns[skillId];
    if (cooldown == nil) then
        cooldown = Cooldown.New();
        fighter.cooldowns[skillId] = cooldown;
    end
    cooldown.start = fighter.fight.frame;
    cooldown.delay = delay;
    cooldown.driginalDelay = originalDelay;
end

--[[
* 是否冷却中
*
* @param fighter 战士
* @param skillId 技能ID
]]
function this.isCooldowning(fighter, skillId)
    local cooldown = fighter.cooldowns[skillId];
    if (cooldown == nil) then
        return false;
    end

    return fighter.fight.frame < cooldown.start + cooldown.delay;
end

function this.cleanCooldown(fighter, skillId)
    local cooldown = fighter.cooldowns[skillId];
    if (cooldown ~= nil) then
        cooldown.delay = 0;
    end
    return true;
end
